Išsami React `useSyncExternalStore` kabliuko analizė, skirta sklandžiai integracijai su išoriniais duomenų šaltiniais ir būsenos valdymo bibliotekomis. Sužinokite, kaip efektyviai valdyti bendrą būseną React programose.
React useSyncExternalStore: Išorinės būsenos integracijos įvaldymas
React useSyncExternalStore kabliukas (hook), pristatytas React 18 versijoje, suteikia galingą ir efektyvų būdą integruoti išorinius duomenų šaltinius ir būsenos valdymo bibliotekas į jūsų React komponentus. Šis kabliukas leidžia komponentams prenumeruoti pokyčius išorinėse saugyklose (stores), užtikrinant, kad vartotojo sąsaja visada atspindėtų naujausius duomenis ir optimizuotų našumą. Šis vadovas pateikia išsamią useSyncExternalStore apžvalgą, apimančią pagrindines sąvokas, naudojimo modelius ir geriausias praktikas.
Kam reikalingas useSyncExternalStore
Daugelyje React programų susidursite su scenarijais, kai būseną reikia valdyti už komponentų medžio ribų. Taip dažnai nutinka dirbant su:
- Trečiųjų šalių bibliotekomis: Integracija su bibliotekomis, kurios valdo savo būseną (pvz., duomenų bazės ryšys, naršyklės API ar fizikos variklis).
- Bendra būsena tarp komponentų: Būsenos valdymas, kurią reikia bendrinti tarp tiesiogiai nesusijusių komponentų (pvz., vartotojo autentifikacijos būsena, programos nustatymai ar globali įvykių magistralė).
- Išoriniais duomenų šaltiniais: Duomenų gavimas ir rodymas iš išorinių API ar duomenų bazių.
Tradiciniai būsenos valdymo sprendimai, tokie kaip useState ir useReducer, puikiai tinka vietinei komponento būsenai valdyti. Tačiau jie nėra skirti efektyviam išorinės būsenos tvarkymui. Jų tiesioginis naudojimas su išoriniais duomenų šaltiniais gali sukelti našumo problemų, nenuoseklių atnaujinimų ir sudėtingo kodo.
useSyncExternalStore sprendžia šias problemas, suteikdamas standartizuotą ir optimizuotą būdą prenumeruoti pokyčius išorinėse saugyklose. Jis užtikrina, kad komponentai būtų perpiešiami tik pasikeitus atitinkamiems duomenims, taip sumažinant nereikalingus atnaujinimus ir pagerinant bendrą našumą.
Pagrindinės useSyncExternalStore sąvokos
useSyncExternalStore priima tris argumentus:
subscribe: Funkcija, kuri priima atgalinio iškvietimo (callback) funkciją kaip argumentą ir prenumeruoja išorinę saugyklą. Atgalinio iškvietimo funkcija bus iškviesta kiekvieną kartą, kai pasikeis saugyklos duomenys.getSnapshot: Funkcija, kuri grąžina duomenų momentinę kopiją (snapshot) iš išorinės saugyklos. Ši funkcija turėtų grąžinti stabilią reikšmę, kurią React gali naudoti nustatant, ar duomenys pasikeitė. Ji turi būti gryna (pure) ir greita.getServerSnapshot(pasirinktinai): Funkcija, kuri grąžina pradinę saugyklos reikšmę serverio pusės generavimo (server-side rendering) metu. Tai yra labai svarbu norint užtikrinti, kad pradinis HTML atitiktų kliento pusės generavimą. Ji naudojama TIK serverio pusės generavimo aplinkose. Jei ji praleidžiama kliento pusės aplinkoje, vietoj jos naudojamagetSnapshot. Svarbu, kad ši reikšmė niekada nepasikeistų po to, kai ji iš pradžių buvo sugeneruota serverio pusėje.
Štai kiekvieno argumento analizė:
1. subscribe
Funkcija subscribe yra atsakinga už ryšio tarp React komponento ir išorinės saugyklos sukūrimą. Ji gauna atgalinio iškvietimo funkciją, kurią turėtų iškviesti, kai tik pasikeičia saugyklos duomenys. Šis atgalinis iškvietimas paprastai naudojamas komponento perpiešimui inicijuoti.
Pavyzdys:
const subscribe = (callback) => {
store.addListener(callback);
return () => {
store.removeListener(callback);
};
};
Šiame pavyzdyje store.addListener prideda atgalinio iškvietimo funkciją į saugyklos klausytojų sąrašą. Funkcija grąžina valymo (cleanup) funkciją, kuri pašalina klausytoją, kai komponentas yra išmontuojamas (unmounts), taip išvengiant atminties nutekėjimo.
2. getSnapshot
Funkcija getSnapshot yra atsakinga už duomenų momentinės kopijos gavimą iš išorinės saugyklos. Ši momentinė kopija turėtų būti stabili reikšmė, kurią React gali naudoti nustatant, ar duomenys pasikeitė. React naudoja Object.is, kad palygintų dabartinę momentinę kopiją su ankstesne. Todėl ji turi būti greita ir labai rekomenduojama, kad ji grąžintų primityvią reikšmę (eilutę, skaičių, loginę reikšmę, null arba undefined).
Pavyzdys:
const getSnapshot = () => {
return store.getData();
};
Šiame pavyzdyje store.getData grąžina dabartinius duomenis iš saugyklos. React palygins šią reikšmę su ankstesne, kad nustatytų, ar komponentą reikia perpiešti.
3. getServerSnapshot (pasirinktinai)
Funkcija getServerSnapshot yra aktuali tik tada, kai naudojamas serverio pusės generavimas (SSR). Ši funkcija iškviečiama pradinio serverio generavimo metu, o jos rezultatas naudojamas kaip pradinė saugyklos reikšmė, kol kliento pusėje įvyksta „hidratacija“ (hydration). Nuoseklių reikšmių grąžinimas yra labai svarbus sėkmingam SSR.
Pavyzdys:
const getServerSnapshot = () => {
return store.getInitialDataForServer();
};
Šiame pavyzdyje `store.getInitialDataForServer` grąžina pradinius duomenis, tinkamus serverio pusės generavimui.
Pagrindinis naudojimo pavyzdys
Panagrinėkime paprastą pavyzdį, kuriame turime išorinę saugyklą, valdančią skaitiklį. Galime naudoti useSyncExternalStore, kad parodytume skaitiklio reikšmę React komponente:
// External store
const createStore = (initialValue) => {
let value = initialValue;
const listeners = new Set();
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const getSnapshot = () => value;
const setState = (newValue) => {
value = newValue;
listeners.forEach((listener) => listener());
};
return {
subscribe,
getSnapshot,
setState,
};
};
const counterStore = createStore(0);
// React component
import React from 'react';
import { useSyncExternalStore } from 'react';
function Counter() {
const count = useSyncExternalStore(counterStore.subscribe, counterStore.getSnapshot);
const increment = () => {
counterStore.setState(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
export default Counter;
Šiame pavyzdyje createStore sukuria paprastą išorinę saugyklą, kuri valdo skaitiklio reikšmę. Komponentas Counter naudoja useSyncExternalStore, kad prenumeruotų saugyklos pokyčius ir rodytų dabartinę skaitiklio reikšmę. Paspaudus didinimo mygtuką, setState funkcija atnaujina saugyklos reikšmę, o tai sukelia komponento perpiešimą.
Integracija su būsenos valdymo bibliotekomis
useSyncExternalStore yra ypač naudingas integruojant su būsenos valdymo bibliotekomis, tokiomis kaip Zustand, Jotai ir Recoil. Šios bibliotekos suteikia savo būsenos valdymo mechanizmus, o useSyncExternalStore leidžia sklandžiai jas integruoti į jūsų React komponentus.
Štai integracijos su Zustand pavyzdys:
import { useStore } from 'zustand';
import { create } from 'zustand';
// Zustand store
const useBoundStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
// React component
function Counter() {
const count = useStore(useBoundStore, (state) => state.count);
const increment = useStore(useBoundStore, (state) => state.increment);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
export default Counter;
Zustand supaprastina saugyklos kūrimą. Vidinės subscribe ir getSnapshot implementacijos yra naudojamos netiesiogiai, kai prenumeruojate tam tikrą būseną.
Štai integracijos su Jotai pavyzdys:
import { atom, useAtom } from 'jotai'
// Jotai atom
const countAtom = atom(0)
// React component
function Counter() {
const [count, setCount] = useAtom(countAtom)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
export default Counter;
Jotai naudoja atomus būsenai valdyti. useAtom viduje tvarko prenumeratą ir momentinių kopijų kūrimą.
Našumo optimizavimas
useSyncExternalStore suteikia keletą mechanizmų našumui optimizuoti:
- Atrankiniai atnaujinimai: React perpiešia komponentą tik tada, kai pasikeičia
getSnapshotgrąžinama reikšmė. Tai užtikrina, kad būtų išvengta nereikalingų perpiešimų. - Atnaujinimų grupavimas: React sugrupuoja atnaujinimus iš kelių išorinių saugyklų į vieną perpiešimą. Tai sumažina perpiešimų skaičių ir pagerina bendrą našumą.
- Pasenusių uždarymų (stale closures) išvengimas:
useSyncExternalStoreužtikrina, kad komponentas visada turėtų prieigą prie naujausių duomenų iš išorinės saugyklos, net ir dirbant su asinchroniniais atnaujinimais.
Norėdami dar labiau optimizuoti našumą, apsvarstykite šias geriausias praktikas:
- Sumažinkite
getSnapshotgrąžinamų duomenų kiekį: Grąžinkite tik tuos duomenis, kurių komponente iš tikrųjų reikia. Tai sumažina lyginamų duomenų kiekį ir pagerina atnaujinimo proceso efektyvumą. - Naudokite memoizacijos technikas: Įsiminkite (memoize) brangių skaičiavimų ar duomenų transformacijų rezultatus. Tai gali užkirsti kelią nereikalingiems perskaičiavimams ir pagerinti našumą.
- Venkite nereikalingų prenumeratų: Prenumeruokite išorinę saugyklą tik tada, kai komponentas yra matomas. Tai gali sumažinti aktyvių prenumeratų skaičių ir pagerinti bendrą našumą.
- Užtikrinkite, kad
getSnapshotgrąžintų naują *stabilų* objektą tik pasikeitus duomenims: Venkite kurti naujus objektus/masyvus/funkcijas, jei pagrindiniai duomenys iš tikrųjų nepasikeitė. Jei įmanoma, grąžinkite tą patį objektą pagal nuorodą.
Serverio pusės generavimas (SSR) su useSyncExternalStore
Naudojant useSyncExternalStore su serverio pusės generavimu (SSR), labai svarbu pateikti getServerSnapshot funkciją. Ši funkcija užtikrina, kad pradinis HTML, sugeneruotas serveryje, atitiktų kliento pusės generavimą, taip išvengiant „hidratacijos“ klaidų ir pagerinant vartotojo patirtį.
Štai pavyzdys, kaip naudoti getServerSnapshot:
const createStore = (initialValue) => {
let value = initialValue;
const listeners = new Set();
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const getSnapshot = () => value;
const getServerSnapshot = () => initialValue; // Important for SSR
const setState = (newValue) => {
value = newValue;
listeners.forEach((listener) => listener());
};
return {
subscribe,
getSnapshot,
getServerSnapshot,
setState,
};
};
const counterStore = createStore(0);
// React component
import React from 'react';
import { useSyncExternalStore } from 'react';
function Counter() {
const count = useSyncExternalStore(counterStore.subscribe, counterStore.getSnapshot, counterStore.getServerSnapshot);
const increment = () => {
counterStore.setState(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
export default Counter;
Šiame pavyzdyje getServerSnapshot grąžina pradinę skaitiklio reikšmę. Tai užtikrina, kad pradinis HTML, sugeneruotas serveryje, atitiktų kliento pusės generavimą. `getServerSnapshot` turėtų grąžinti stabilią ir nuspėjamą reikšmę. Ji taip pat turėtų atlikti tą pačią logiką kaip ir getSnapshot funkcija serveryje. Venkite naudoti naršyklei specifines API ar globalius kintamuosius getServerSnapshot funkcijoje.
Pažangesni naudojimo modeliai
useSyncExternalStore galima naudoti įvairiuose pažangesniuose scenarijuose, įskaitant:
- Integracija su naršyklės API: Prenumeruoti pokyčius naršyklės API, pavyzdžiui,
localStoragearnavigator.onLine. - Individualių kabliukų (Custom Hooks) kūrimas: Išorinės saugyklos prenumeratos logikos inkapsuliavimas į individualų kabliuką.
- Naudojimas su Context API:
useSyncExternalStorederinimas su React Context API, siekiant suteikti bendrą būseną komponentų medžiui.
Panagrinėkime pavyzdį, kaip sukurti individualų kabliuką, prenumeruojantį localStorage:
import { useSyncExternalStore } from 'react';
function useLocalStorage(key, initialValue) {
const getSnapshot = () => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error("Error getting value from localStorage:", error);
return initialValue;
}
};
const subscribe = (callback) => {
window.addEventListener('storage', callback);
return () => window.removeEventListener('storage', callback);
};
const setItem = (value) => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
window.dispatchEvent(new Event('storage')); // Manually trigger storage event for same-page updates
} catch (error) {
console.error("Error setting value in localStorage:", error);
}
};
const serverSnapshot = () => initialValue;
const storedValue = useSyncExternalStore(subscribe, getSnapshot, serverSnapshot);
return [storedValue, setItem];
}
export default useLocalStorage;
Šiame pavyzdyje useLocalStorage yra individualus kabliukas, kuris prenumeruoja localStorage pokyčius. Jis naudoja useSyncExternalStore prenumeratai valdyti ir dabartinei reikšmei iš localStorage gauti. Jis taip pat teisingai išsiunčia `storage` įvykį, kad užtikrintų atnaujinimų atspindėjimą tame pačiame puslapyje (kadangi `storage` įvykiai yra paleidžiami tik kitose naršyklės kortelėse). `serverSnapshot` užtikrina, kad pradinės reikšmės būtų teisingai pateiktos serverio aplinkose.
Geriausios praktikos ir dažniausios klaidos
Štai keletas geriausių praktikų ir dažniausiai pasitaikančių klaidų, kurių reikėtų vengti naudojant useSyncExternalStore:
- Venkite tiesiogiai keisti išorinę saugyklą: Duomenims atnaujinti visada naudokite saugyklos API. Tiesioginis saugyklos keitimas gali sukelti nenuoseklius atnaujinimus ir netikėtą elgesį.
- Užtikrinkite, kad
getSnapshotbūtų gryna (pure) ir greita:getSnapshotneturėtų turėti šalutinių poveikių ir turėtų greitai grąžinti stabilią reikšmę. Brangūs skaičiavimai ar duomenų transformacijos turėtų būti įsiminti (memoized). - Pateikite
getServerSnapshotfunkciją, kai naudojate SSR: Tai yra labai svarbu norint užtikrinti, kad pradinis HTML, sugeneruotas serveryje, atitiktų kliento pusės generavimą. - Korektiškai apdorokite klaidas: Naudokite try-catch blokus galimoms klaidoms, kylančioms prieigai prie išorinės saugyklos, apdoroti.
- Išvalykite prenumeratas: Visada atsisakykite prenumeratos iš išorinės saugyklos, kai komponentas yra išmontuojamas, kad išvengtumėte atminties nutekėjimo. Funkcija
subscribeturėtų grąžinti valymo funkciją, kuri pašalina klausytoją. - Supraskite našumo pasekmes: Nors
useSyncExternalStoreyra optimizuotas našumui, svarbu suprasti galimą prenumeratos poveikį išorinėms saugykloms. SumažinkitegetSnapshotgrąžinamų duomenų kiekį ir venkite nereikalingų prenumeratų. - Testuokite kruopščiai: Įsitikinkite, kad integracija su saugykla veikia teisingai įvairiuose scenarijuose, ypač serverio pusės generavimo ir konkurentiniame (concurrent) režime.
Išvada
useSyncExternalStore yra galingas ir efektyvus kabliukas, skirtas integruoti išorinius duomenų šaltinius ir būsenos valdymo bibliotekas į jūsų React komponentus. Suprasdami jo pagrindines sąvokas, naudojimo modelius ir geriausias praktikas, galite efektyviai valdyti bendrą būseną savo React programose ir optimizuoti našumą. Nesvarbu, ar integruojate su trečiųjų šalių bibliotekomis, valdote bendrą būseną tarp komponentų, ar gaunate duomenis iš išorinių API, useSyncExternalStore suteikia standartizuotą ir patikimą sprendimą. Pasinaudokite juo, kad kurtumėte tvirtesnes, lengviau prižiūrimas ir našesnes React programas pasaulinei auditorijai.